package audit

import (
	"encoding/json"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	"chainmaker.org/chainmaker/smarteditor/conf"
	"chainmaker.org/chainmaker/smarteditor/logger"
	"chainmaker.org/chainmaker/smarteditor/session"
	"chainmaker.org/chainmaker/smarteditor/util"
	"github.com/88250/gulu"
)

var log = logger.Get()

// 合约代码审计接口
func AuditHandler(w http.ResponseWriter, r *http.Request) {
	// 取参数,校验参数
	uid, err := session.GetUidFromSession(r)
	if err != nil {
		log.Errorf("DeployHandler Forbidden. url:%s, uid:%s, err:%v", r.URL, uid, err)
		http.Error(w, "Forbidden", http.StatusForbidden)
		return
	}
	result := gulu.Ret.NewResult()
	defer gulu.Ret.RetResult(w, r, result)
	var args map[string]interface{}
	if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
		log.Errorf("DeployHandler decode parameter failed. url:%s, err:%v", r.URL, err)
		result.Code = -1
		return
	}
	contractName := args["contractName"].(string)
	sid := args["sid"].(string)
	if contractName == "" || sid == "" {
		http.Error(w, "parameter error", http.StatusBadRequest)
		return
	}
	workspace := conf.GetUserWorkspace(uid)
	userSrcPath := conf.GetSrcDir(workspace)
	srcPath := conf.GetSrcContractDir(workspace, contractName)
	if !util.FileExists(srcPath) {
		// 说明不存在合约
		http.Error(w, "no contract : "+contractName, http.StatusBadRequest)
		return
	}
	// 下面检查一下是否开启了audit
	if !conf.Wide.AuditEnable {
		// 未开启代码审核
		return
	}
	// 下面就可以开始是异步的了
	uploadPath := conf.GetUploadContractDir(workspace, contractName)
	uploadWorkSpace := conf.GetUploadDir(workspace)
	if !util.FileExists(uploadWorkSpace) {
		err := conf.CreateUploadDir(workspace)
		if err != nil {
			log.Errorf("AuditHandler CreateUploadDir %s failed %s", workspace, err.Error())
			result.Code = -1
			return
		}
	}

	// 拷贝合约源码文件到upload
	copyErr := util.CopyDirSkip(srcPath, uploadPath, map[string]struct{}{contractName: struct{}{}})
	if copyErr != nil {
		log.Errorf("AuditHandler CopyDirSkip src: %s, dest: %s, failed: %s",
			srcPath, uploadPath, copyErr.Error())
		result.Code = -1
		return
	}
	defer os.RemoveAll(uploadPath)
	// 压缩upload下的合约文件
	zipPath, zipErr := util.CompressZip(uploadPath, uploadWorkSpace)
	if zipErr != nil {
		log.Errorf("AuditHandler CompressZip %s , failed %s", uploadPath, zipErr.Error())
		result.Code = -1
		return
	}
	defer os.Remove(zipPath)
	// 调用第三方接口 upload
	fileId, _, uploadErr := auditProcessor.UploadContractZip(zipPath, contractName+".zip")
	if uploadErr != nil {
		log.Errorf("AuditHandler UploadContractZip %s failed %s", zipPath, uploadErr.Error())
		result.Code = -1
		return
	}
	// 调用第三方接口 audit,
	taskId, _, auditFileIdErr := auditProcessor.AuditUseFileId(fileId)
	if uploadErr != nil {
		log.Errorf("AuditHandler AuditUseFileId %s failed %s", fileId, auditFileIdErr.Error())
		result.Code = -1
		return
	}
	// ws 开始
	sendBeginAudit(uid, sid, contractName)
	// todo ws
	go notifyAuditStatus(uid, sid, taskId, userSrcPath, contractName)
}

func generateRequestId(path string) string {
	origin := path + strconv.FormatInt(time.Now().UnixNano(), 10)
	if len(origin) > 60 {
		return origin[:60]
	}
	return origin
}

func notifyAuditStatus(uid, sid, taskId, userSrcPath, contractName string) {
	hasCalled := 0
	tk := time.NewTicker(time.Second)
	defer tk.Stop()
	for {
		select {
		case <-tk.C:
			hasCalled++
			// 将返回数据通过ws发送给前端
			retData, queryErr := auditProcessor.QueryAuditStatusByTaskId(taskId, userSrcPath)
			if queryErr != nil {
				log.Errorf("AuditHandler QueryAuditStatusByTaskId %s failed %s",
					taskId, queryErr.Error())
			} else if !strings.Contains(retData.Status, "审计完成") {
				hasCalled++
				log.Debugf("uid %s ,taskId is on auditing", uid, taskId)
				continue
			} else {
				sendEndAudit(uid, sid, retData, contractName)
				return
			}
		default:
			if hasCalled > 30 {
				log.Warnf("notifyAuditStatus uid %s, taskId %s, wait nothing for 10 times",
					uid, taskId)
				sendAuditError(uid, sid, contractName)
				return
			}
		}

	}
}

func sendBeginAudit(uid, sid, contractName string) {
	defer util.PanicLog()
	user := conf.GetUser(uid)
	if user == nil {
		log.Errorf("sendBeginAudit find no user uid %s ", uid)
		return
	}
	channelRet := map[string]interface{}{}
	channelRet["output"] = "<span class='start-audit'>" + "代码漏洞审查服务执行中(" + contractName + ") </span>\n"
	channelRet["cmd"] = "start-audit"
	wsChannel, err := session.GetOutputWS(sid)
	if err == nil {
		err := wsChannel.WriteJSON(&channelRet)
		if err != nil {
			log.Warnf("sendBeginAudit faild. err:%v", err)
		} else {
			wsChannel.Refresh()
		}
	} else {
		log.Warnf("sendBeginAudit data to channel, channel is nil")
	}
}

func sendEndAudit(uid, sid string, auditData *TaskData, contractName string) {
	defer util.PanicLog()
	user := conf.GetUser(uid)
	if user == nil {
		log.Errorf("sendEndAudit find no user uid %s ", uid)
		return
	}
	channelRet := map[string]interface{}{}
	notips := ""
	if auditData != nil && len(auditData.Result) == 0 {
		notips = "，且没有发现漏洞"
	}
	channelRet["output"] = "<span class='end-audit'>" + "代码漏洞审查服务执行成功(" + contractName + ")" + notips + "</span>\n"
	channelRet["cmd"] = "end-audit"
	channelRet["data"] = auditData
	wsChannel, err := session.GetOutputWS(sid)
	if err == nil {
		err := wsChannel.WriteJSON(&channelRet)
		if err != nil {
			log.Warnf("sendEndAudit faild. err:%v", err)
		} else {
			wsChannel.Refresh()
		}
	} else {
		log.Warnf("sendEndAudit data to channel, channel is nil")
	}
}

func sendAuditError(uid, sid, contractName string) {
	defer util.PanicLog()
	channelRetOut := map[string]interface{}{}
	channelRetOut["cmd"] = "audit-failed"
	channelRetOut["output"] = "<span class='stderr'> " + "代码漏洞审查服务执行失败(" + contractName + ") </span>"
	wsChannel, err := session.GetOutputWS(sid)
	if err != nil {
		log.Warnf("send audit message to ws, channel is nil. uid:%s, sid:%s", uid, sid)
		return
	}
	err = wsChannel.WriteJSON(&channelRetOut)
	if nil != err {
		log.Warnf("send audit message to ws. uid:%s, sid:%s, err:%v", uid, sid, err)
	}
	wsChannel.Refresh()
}
